package task;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * TODO
 *
 * @author ouyf
 * @date 11/18/2025 10:37
 * <p>
 */
public class GitLabReporter {
    // --- 配置信息 ---
    private static final String GITLAB_BASE_URL = "https://code.siemens.com"; // 替换为你的 GitLab 实例 URL
    private static final String PRIVATE_TOKEN = "CSC-glpat-SKa52yzJQ8hUvSY13VDzcG86MQp1OjI4cTQK.01.100j999f6"; // 替换为你的 GitLab 私有访问令牌
    // 替换为你的目标命名空间（通常是 Group 的完整路径，例如 "my-company/devops-team"）
    private static final String TARGET_NAMESPACE_PATH = "tbds/general";

    private final String baseUrl;
    private final String privateToken;
    private final Gson gson;

    public GitLabReporter(String baseUrl, String privateToken) {
        this.baseUrl = baseUrl;
        this.privateToken = privateToken;
        this.gson = new Gson();
    }

    // --- JSON 数据模型 ---

    /**
     * 表示一个 GitLab 项目的简化模型。
     */
    static class GitLabProject {
        int id;
        String name;
        String path_with_namespace;
        Owner owner; // 项目所有者

        public int getId() { return id; }
        public String getName() { return name; }
        public String getPathWithNamespace() { return path_with_namespace; }
        public Owner getOwner() { return owner; }

        @Override
        public String toString() {
            return "GitLabProject{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", path_with_namespace='" + path_with_namespace + '\'' +
                    ", owner=" + owner +
                    '}';
        }
    }

    /**
     * 表示项目所有者。
     */
    static class Owner {
        String name;

        public String getName() { return name; }

        @Override
        public String toString() {
            return "Owner{name='" + name + '\'' + '}';
        }
    }

    /**
     * 表示一个 GitLab 分支的简化模型。
     */
    static class GitLabBranch {
        String name;
        Commit commit; // 分支指向的提交

        public String getName() { return name; }
        public Commit getCommit() { return commit; }
    }

    /**
     * 表示一个 Git 提交的简化模型。
     */
    static class Commit {
        String id; // 提交的 Git Hash

        public String getId() { return id; }
    }

    /**
     * 用于 CSV 报告的最终数据结构。
     */
    static class ProjectReportEntry {
        String projectName;
        String gitHash;
        String ownerName;

        public ProjectReportEntry(String projectName, String gitHash, String ownerName) {
            this.projectName = projectName;
            this.gitHash = gitHash;
            this.ownerName = ownerName;
        }

        /**
         * 获取 CSV 文件的表头。
         */
        public static String getCsvHeader() {
            return "Project Name,Git Hash,Owner Name";
        }

        /**
         * 将当前条目转换为 CSV 行。
         * 会对包含逗号、双引号或换行符的字段进行转义。
         */
        public String toCsvRow() {
            return String.format("\"%s\",\"%s\",\"%s\"",
                    escapeCsv(projectName),
                    escapeCsv(gitHash),
                    escapeCsv(ownerName));
        }

        /**
         * CSV 值转义辅助方法。
         * 如果值包含逗号、双引号或换行符，则用双引号括起来，并对内部的双引号进行双重转义。
         */
        private static String escapeCsv(String value) {
            if (value == null) {
                return "";
            }
            if (value.contains(",") || value.contains("\"") || value.contains("\n")) {
                return value.replace("\"", "\"\"");
            }
            return value;
        }
    }

    // --- API 客户端方法 ---

    /**
     * 发送 HTTP GET 请求到 GitLab API。
     *
     * @param endpoint API 端点 (例如 "/api/v4/groups")
     * @param params   查询参数 (例如 "search=my-group")
     * @return JSON 响应字符串，如果发生错误则返回 null。
     * @throws IOException 如果发生网络或 I/O 错误。
     */
    private String sendGetRequest(String endpoint, String params) throws IOException {
        String urlString = baseUrl + endpoint + (params != null && !params.isEmpty() ? "?" + params : "");
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setRequestProperty("PRIVATE-TOKEN", privateToken); // 添加认证头
        connection.setRequestProperty("Accept", "application/json"); // 接受 JSON 格式响应

        int responseCode = connection.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // 读取成功响应
            try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
                StringBuilder response = new StringBuilder();
                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                return response.toString();
            }
        } else {
            // 处理错误响应
            System.err.println("Error fetching data from " + urlString + ". Response Code: " + responseCode);
            try (BufferedReader err = new BufferedReader(new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) {
                String errorLine;
                StringBuilder errorResponse = new StringBuilder();
                while ((errorLine = err.readLine()) != null) {
                    errorResponse.append(errorLine);
                }
                System.err.println("Error details: " + errorResponse.toString());
            }
            return null;
        }
    }

    /**
     * 根据 Group 的完整路径查找其 ID。
     *
     * @param groupPath Group 的完整路径 (例如 "my-org/my-subgroup")。
     * @return 如果找到，则返回包含 Group ID 的 Optional，否则返回空的 Optional。
     * @throws IOException 如果发生网络或 I/O 错误。
     */
    public Optional<Integer> getGroupIdByPath(String groupPath) throws IOException {
        String encodedGroupPath = URLEncoder.encode(groupPath, StandardCharsets.UTF_8.toString());
        String params = "search=" + encodedGroupPath; // GitLab 的 /groups API 使用 'search' 来查找名称/路径
        String jsonResponse = sendGetRequest("/api/v4/groups", params);

        if (jsonResponse != null) {
            // Gson 默认将数字解析为 Double，所以这里用 Map<String, Object> 来处理
            Type groupListType = new TypeToken<List<Map<String, Object>>>(){}.getType();
            List<Map<String, Object>> groups = gson.fromJson(jsonResponse, groupListType);

            // 过滤以找到确切的 group_path
            return groups.stream()
                    .filter(g -> groupPath.equals(g.get("full_path")))
                    .map(g -> ((Double) g.get("id")).intValue()) // 将 Double 转换为 int
                    .findFirst();
        }
        return Optional.empty();
    }

    /**
     * 获取指定 GitLab Group 下的所有项目。处理分页。
     *
     * @param groupId GitLab Group 的 ID。
     * @return GitLabProject 对象的列表。
     * @throws IOException 如果发生网络或 I/O 错误。
     */
    public List<GitLabProject> getProjectsInGroup(int groupId) throws IOException {
        List<GitLabProject> allProjects = new ArrayList<>();
        int page = 1;
        boolean hasMorePages = true;

        while (hasMorePages) {
            String params = String.format("per_page=100&page=%d", page); // GitLab API 最大 per_page 为 100
            String endpoint = String.format("/api/v4/groups/%d/projects", groupId);
            String jsonResponse = sendGetRequest(endpoint, params);

            if (jsonResponse != null) {
                Type projectListType = new TypeToken<List<GitLabProject>>(){}.getType();
                List<GitLabProject> projectsOnPage = gson.fromJson(jsonResponse, projectListType);
                allProjects.addAll(projectsOnPage);

                // 判断是否还有更多页：如果当前页返回的项目数小于 per_page，则说明是最后一页
                if (projectsOnPage.size() < 100) {
                    hasMorePages = false;
                } else {
                    page++;
                }
            } else {
                hasMorePages = false; // 如果请求出错，停止分页
            }
        }
        return allProjects;
    }

    /**
     * 获取指定项目特定分支的最新提交 Hash。
     *
     * @param projectId 项目 ID。
     * @param branchName 分支名称 (例如 "main")。
     * @return 如果找到，则返回包含提交 Hash 字符串的 Optional，否则返回空的 Optional。
     * @throws IOException 如果发生网络或 I/O 错误。
     */
    public Optional<String> getBranchHeadCommitHash(int projectId, String branchName) throws IOException {
        String encodedBranchName = URLEncoder.encode(branchName, StandardCharsets.UTF_8.toString());
        String endpoint = String.format("/api/v4/projects/%d/repository/branches/%s", projectId, encodedBranchName);
        String jsonResponse = sendGetRequest(endpoint, null);

        if (jsonResponse != null) {
            GitLabBranch branch = gson.fromJson(jsonResponse, GitLabBranch.class);
            if (branch != null && branch.getCommit() != null) {
                return Optional.ofNullable(branch.getCommit().getId());
            }
        }
        return Optional.empty();
    }

    // --- 主程序逻辑 ---

    public static void main(String[] args) {
        // 检查配置是否已更新
        if (PRIVATE_TOKEN.equals("YOUR_PRIVATE_ACCESS_TOKEN") || TARGET_NAMESPACE_PATH.equals("your-group/your-subgroup")) {
            System.err.println("错误：请在运行前配置 GITLAB_BASE_URL, PRIVATE_TOKEN 和 TARGET_NAMESPACE_PATH。");
            System.exit(1);
        }

        GitLabReporter reporter = new GitLabReporter(GITLAB_BASE_URL, PRIVATE_TOKEN);
        List<ProjectReportEntry> reportEntries = new ArrayList<>();

        try {
            System.out.println("步骤 1: 正在查找命名空间 '" + TARGET_NAMESPACE_PATH + "' 的 Group ID...");
            Optional<Integer> groupIdOpt = reporter.getGroupIdByPath(TARGET_NAMESPACE_PATH);

            if (!groupIdOpt.isPresent()) {
                System.err.println("错误：未找到路径为 '" + TARGET_NAMESPACE_PATH + "' 的 Group，或无权访问。");
                return;
            }
            int groupId = groupIdOpt.get();
            System.out.println("已找到 Group ID: " + groupId);

            System.out.println("步骤 2: 正在获取 Group ID " + groupId + " 下的所有项目...");
            List<GitLabProject> projects = reporter.getProjectsInGroup(groupId);
            System.out.println("已找到 " + projects.size() + " 个项目。");

            if (projects.isEmpty()) {
                System.out.println("在指定 Group 中未找到任何项目。");
                return;
            }

            System.out.println("步骤 3: 正在收集每个项目的 'main' 分支信息...");
            for (GitLabProject project : projects) {
                System.out.print("  处理项目: " + project.getName() + " (ID: " + project.getId() + ")");
                String ownerName = (project.getOwner() != null) ? project.getOwner().getName() : "N/A";
                // 假设要获取 'main' 分支的 Git Hash
                Optional<String> mainBranchHash = reporter.getBranchHeadCommitHash(project.getId(), "main");

                if (mainBranchHash.isPresent()) {
                    reportEntries.add(new ProjectReportEntry(project.getName(), mainBranchHash.get(), ownerName));
                    System.out.println(" -> Main 分支 Hash: " + mainBranchHash.get());
                } else {
                    reportEntries.add(new ProjectReportEntry(project.getName(), "N/A (main 分支未找到或错误)", ownerName));
                    System.out.println(" -> Main 分支 Hash: N/A (main 分支未找到或错误)");
                }
            }

            String csvFileName = "gitlab_projects_report.csv";
            System.out.println("\n步骤 4: 正在生成 CSV 报告文件: " + csvFileName);
            writeCsvReport(reportEntries, csvFileName);
            System.out.println("报告生成成功！文件保存在当前运行目录下。");

        } catch (IOException e) {
            System.err.println("发生 I/O 错误: " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            System.err.println("发生未知错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 将收集到的项目信息写入 CSV 文件。
     *
     * @param entries 待写入的 ProjectReportEntry 列表。
     * @param fileName CSV 文件名。
     * @throws IOException 如果文件写入过程中发生错误。
     */
    private static void writeCsvReport(List<ProjectReportEntry> entries, String fileName) throws IOException {
        try (FileWriter writer = new FileWriter(fileName, false)) {
            // 写入表头
            writer.append(ProjectReportEntry.getCsvHeader());
            writer.append("\n");

            // 写入数据行
            for (ProjectReportEntry entry : entries) {
                writer.append(entry.toCsvRow());
                writer.append("\n");
            }
        }
    }
}
